/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

import { TypedEventEmitter } from "@fluid-internal/client-utils";
import type {
	AttachState,
	IAudience,
	IContainerEvents,
} from "@fluidframework/container-definitions/internal";
import { ConnectionState } from "@fluidframework/container-loader";
import { ContainerRuntime } from "@fluidframework/container-runtime/internal";
import type { IContainerRuntime } from "@fluidframework/container-runtime-definitions/internal";
import type { IFluidLoadable } from "@fluidframework/core-interfaces";
import { assert } from "@fluidframework/core-utils/internal";

import { BaseDevtools } from "./BaseDevtools.js";
import type { ContainerKey } from "./CommonInterfaces.js";
import type { DecomposedContainer } from "./DecomposedContainer.js";
import type { ContainerDevtoolsFeatureFlags } from "./Features.js";
import type { InboundHandlers } from "./messaging/index.js";

/**
 * Properties for registering a container runtime with the Devtools.
 * @beta
 * @sealed
 * @input
 */
export interface ContainerRuntimeProps {
	/**
	 * The container runtime to register with the Devtools.
	 */
	readonly runtime: IContainerRuntime;

	/**
	 * Optional label for the container runtime replacing the guid generated by {@link @fluidframework/container-runtime#IContainerRuntime.generateDocumentUniqueId}.
	 */
	readonly label?: string;
}

/**
 * Properties for constructing a {@link ContainerRuntimeDevtools} instance.
 */
export interface ContainerRuntimeDevtoolsConstructorProps {
	/**
	 * The container key to use for this container runtime.
	 */
	containerKey: ContainerKey;

	/**
	 * The decomposed container wrapping the container runtime.
	 */
	container: DecomposedContainer;

	/**
	 * (optional) Data associated with the container runtime.
	 */
	containerData?: Record<string, IFluidLoadable>;
}

/**
 * {@link IContainerDevtools} implementation for container runtimes.
 *
 * @remarks
 *
 * This class provides devtools functionality for container runtimes that don't support
 * container-level operations like connect/disconnect/close. Container runtimes represent
 * the internal runtime state and don't emit "closed" events like IContainer does.
 *
 * @sealed
 */
export class ContainerRuntimeDevtools extends BaseDevtools<DecomposedContainer> {
	/**
	 * The registered container runtime's decomposed container.
	 */
	protected override get container(): DecomposedContainer {
		return this._container;
	}

	private readonly _container: DecomposedContainer;

	public constructor(props: ContainerRuntimeDevtoolsConstructorProps) {
		// Container runtimes don't support connection operations, so pass empty handlers
		const specificHandlers: InboundHandlers = {};

		super(props.containerKey, specificHandlers, props.containerData);

		this._container = props.container;

		// Bind container and audience events after container is set
		this.bindContainerEvents();
		this.bindAudienceEvents();
	}

	protected override getSupportedFeatures(): ContainerDevtoolsFeatureFlags {
		return {
			containerDataVisualization: this.containerData !== undefined,
			// Container runtimes do not have direct control over container lifecycle
			canModifyContainerState: false,
		};
	}

	/**
	 * {@inheritDoc IContainerDevtools.isClosed}
	 */
	public isClosed(): boolean {
		// Container runtimes cannot be closed directly
		return false;
	}
}

/**
 * Implementation of {@link DecomposedContainer} that wraps an {@link @fluidframework/container-runtime-definitions/internal#IContainerRuntime}.
 * @remarks
 * This class provides a bridge between {@link @fluidframework/container-runtime-definitions/internal#IContainerRuntime} and the devtools system by exposing runtime properties and events.
 */
export class DecomposedContainerForContainerRuntime
	extends TypedEventEmitter<IContainerEvents>
	implements DecomposedContainer
{
	private _disposed = false; // Track actual disposed state

	public constructor(runtime: IContainerRuntime) {
		super();
		this.runtime = runtime;
		// Note: IContainerRuntime doesn't emit "closed" events like IContainer does
		// Only bind to events that IContainerRuntime actually supports
		runtime.on("attached", this.attachedHandler);
		runtime.on("connected", this.connectedHandler);
		runtime.on("disconnected", this.disconnectedHandler);
		runtime.on("dispose", this.disposedHandler);
	}

	private readonly attachedHandler = (): boolean => this.emit("attached");
	private readonly connectedHandler = (clientId: string): boolean =>
		this.emit("connected", clientId);
	private readonly disconnectedHandler = (): boolean => this.emit("disconnected");
	private readonly disposedHandler = (): boolean => {
		// IContainerRuntime emits "dispose" (no error) but we emit "disposed" to match IContainerEvents
		// Since IContainerRuntime doesn't provide error info, we emit without error parameter
		this._disposed = true; // Mark as disposed when dispose event occurs
		return this.emit("disposed");
	};

	private readonly runtime: IContainerRuntime;

	public get audience(): IAudience {
		return this.runtime.getAudience();
	}

	public get clientId(): string | undefined {
		return this.runtime.clientId;
	}

	public get attachState(): AttachState {
		return this.runtime.attachState;
	}

	public get connectionState(): ConnectionState {
		// Normal connection state logic - readonly state is handled separately via readOnlyInfo
		return this.runtime.connected ? ConnectionState.Connected : ConnectionState.Disconnected;
	}

	public get closed(): boolean {
		return this._disposed; // IContainerRuntime doesn't have a "closed" state - only "disconnected" (reconnectable) and "disposed" (permanent)
	}

	public get readOnlyInfo(): { readonly readonly?: boolean } {
		// IContainerRuntime doesn't expose readonly in its interface, but the implementation has isReadOnly()
		assert(
			this.runtime instanceof ContainerRuntime,
			0xc2e /* DecomposedContainerForContainerRuntime is not a ContainerRuntime */,
		);
		return { readonly: this.runtime.isReadOnly() };
	}
}
